/**
 * This file is part of the NatCrackerToolbox.
 * 
 * Copyright (C) 2009 - 2011 Peerialism Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 **/
package com.peerialism.natcracker.discovery;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NetworkUtils {
	private static Logger log = LoggerFactory.getLogger(NetworkUtils.class);

	private static Set<InetAddress> validAddresses;

	/**
	 * Finds all the non-virtual/non-loopback IP addresses owned by interfaces belonging to this host
	 * 
	 * @return the {@link Set} if address
	 */
	public static Set<InetAddress> findValidAddresses() {

		final Set<InetAddress> localValidAddresses = new HashSet<InetAddress>();

		Enumeration<NetworkInterface> ifaces = null;
		try {
			ifaces = NetworkInterface.getNetworkInterfaces();
		} catch (final SocketException e) {
			log.error("System Interfaces query failed");
			return null;
		}

		// workaround for linux not detecting 127.0.0.1 as local by isLoopbackAddress()
		InetAddress loopback1 = null, loopback2 = null;
		try {
			loopback1 = InetAddress.getByName("127.0.0.1");
			loopback2 = InetAddress.getByName("127.0.1.1");
		} catch (final UnknownHostException e) {
		}

		while (ifaces.hasMoreElements()) {
			final NetworkInterface iface = ifaces.nextElement();
			try {
				if (iface.isVirtual() || !iface.isUp())
					continue;
			} catch (final SocketException e) {
				e.printStackTrace();
			}
			final List<InterfaceAddress> iaddresseses = iface.getInterfaceAddresses();
			final ListIterator<InterfaceAddress> iaddresses = iaddresseses.listIterator();
			while (iaddresses.hasNext()) {
				final InetAddress iaddress = iaddresses.next().getAddress();
				if (iaddress instanceof Inet4Address) {
					if (!iaddress.isLoopbackAddress() && (loopback1 == null || !iaddress.equals(loopback1))
							&& (loopback2 == null || !iaddress.equals(loopback2))) {
						localValidAddresses.add(iaddress);
					}
				}
			}
		}
		validAddresses = localValidAddresses;
		return localValidAddresses;

	}

	public static boolean isAutomaticPrivate(final InetAddress iaddress) {
		return iaddress.getHostAddress().startsWith("169");
	}

	/**
	 * Finds the public address of this host, if any
	 * 
	 * @return the address
	 */
	public static InetAddress findPublicAddress() {
		Set<InetAddress> addresses = validAddresses;
		if (addresses == null)
			addresses = findValidAddresses();
		if (addresses == null)
			return null;
		for (final InetAddress address : addresses) {
			if (!isAutomaticPrivate(address) && !address.isSiteLocalAddress()) {
				return address;
			}
		}
		return null;
	}

	/**
	 * Finds the private address of this host
	 * 
	 * @return the address
	 */
	public static InetAddress findPrivateAddress() {
		Set<InetAddress> addresses = validAddresses;
		if (addresses == null)
			addresses = findValidAddresses();
		if (addresses == null)
			return null;
		for (final InetAddress address : addresses) {
			if (!address.isLoopbackAddress()) {
				return address;
			}
		}
		return null;
	}

	public static boolean hasIp(InetAddress ip) throws SocketException {
		if (ip == null)
			return false;
		NetworkInterface nic = NetworkInterface.getByInetAddress(ip);
		return (nic != null && nic.isUp());
	}

}
